Vert.x - Web API Contract 错误处理
Vert.x Web API Contract模块在Vert.x Web的基础上进行扩展,支持OpenAPI 3.0规范。
使用上有两种方式
编程方式
预定义
HTTPRequestValidationHandler
,并在route中传入,就像手册给的那样配置文件方式
预先定义好接口描述文件,通过
OpenAPI3RouterFactory
加载并挂载到Router上1
2
3
4
5
6
7
8
9
10
11
12
13
14val openAPI3RouterFactoryList = mutableListOf<OpenAPI3RouterFactory>()
listOf(
"/webroot/swagger/openapi-admin.yaml"
).forEach { configPath ->
awaitResult<OpenAPI3RouterFactory> {
OpenAPI3RouterFactory.create(vertx, configPath, it)
}.apply {
openAPI3RouterFactoryList.add(this)
}
}
val mainRouter = Router.router(vertx)
openAPI3RouterFactoryList.forEach { routerFactory ->
mainRouter.mountSubRouter("/", routerFactory.mountServicesFromExtensions()..router)
}通过这种方式加载,Vert.x能够自动解析描述文件,提供自动挂载验证handler和securityHandler的能力,在请求不符合配置文件定义的约束时,能够自动以合适的状态码回绝用户请求。
问题
使用配置文件方式生成Route,不增加额外Handler的情况下,在遇到验证失败或空指针异常之类的情况时,Vert.x处理验证错误和内部错误的方式是直接报400 Bad Request和500 Internal Error,没有任何附加信息,日志上也不会有任何输出,这为问题排查和API使用者都是很不友好的方式。
解决方案
通过查看手册和源码跟踪,找到如下处理方式,在Router上挂载一个全局的400和500错误处理器,对错误信息进行详细解析
1 | val OpenAPIErrorTypeMap = mapOf( |
如此,若再发生请求参数验证错误,将给出明确的问题所在,而不是靠猜测。
方案出处
遇到这个问题时,我个人倾向于Vert.x应该会提供一个打印详细报错信息的开关之类的东西,但在手册中并没有找到,于是通过源码定位到如下报错地点。
io.vertx.ext.web.api.validation.impl.BaseValidationHandler#handle()
1 |
|
可以看到,在验证失败时,它直接将routingContext设置为了400错误,并将异常一并传入,查看routingContext.fail()方法定义,明确说明,如果没有任何错误处理器对该状态码进行处理,将直接向客户端响应状态码对应的默认响应,对于400的默认响应,就是statucode=400和statusmessage=Bad Request
1 | /** |
因此,就此处来讲,定义针对400的处理器是非常有必要的,同时也是官方推荐的处理方式,官方文档中特意提到了错误处理的管理方式,有如下两种,很明显,对于很多path的情况,使用第二种更好。
单独为一个路径增加错误处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15router.get("/awesome/:pathParam")
// Mount validation handler
.handler(validationHandler)
//Mount your handler
.handler((routingContext) -> {
// Your logic
})
//Mount your failure handler to manage the validation failure at path level
.failureHandler((routingContext) -> {
Throwable failure = routingContext.failure();
if (failure instanceof ValidationException) {
// Something went wrong during validation!
String validationErrorMessage = failure.getMessage();
}
});为一个状态码增加错误处理器
1
2
3
4
5
6
7
8
9
10// Manage the validation failure for all routes in the router
router.errorHandler(400, routingContext -> {
if (routingContext.failure() instanceof ValidationException) {
// Something went wrong during validation!
String validationErrorMessage = routingContext.failure().getMessage();
} else {
// Unknown 400 failure happened
routingContext.response().setStatusCode(400).end();
}
});
延伸
关于在Router上为某个特定的状态码增加错误处理器的处理,不仅在于此处,个人认为是可以通用的,对高频发生的状态码,可以这样增加一个全局处理器,使得不至于丢失错误信息。
此外,这也衍生出另一个问题,Vert.x的全局错误处理,对于运行中错误的漏网之鱼,要定义合适有效的全局处理器,使得不放过任何错误,这一点要注意。